<?php
namespace tools;

/**
 * 微信App支付
 *
 */
use Org\Util\Date;
use think\facade\Log;

use GuzzleHttp\Exception\RequestException;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use GuzzleHttp\HandlerStack;

class WxappApi {

    protected $APPID;
    protected $MCHID;
    protected $KEY;
    protected $notify_url;

    public function __construct($APPID,$MCHID,$KEY,$notify_url='')
    {
        $this->APPID  = $APPID;
        $this->MCHID = $MCHID;
        $this->KEY = $KEY;
        $this->notify_url = $notify_url;
    }

    /**
     * 微信唤起支付
     * @return goodsname 商品名称
     * @return order_no 订单编号
     * @return pay_money 支付金额
     */
  public function prePay($goodsname,$order_no,$pay_money='') {
      $json_data=[
          "mchid" =>(string)$this->MCHID,//特约商户商户号
          "appid" =>$this->APPID, 
          "description" =>$goodsname, 
          "out_trade_no" => $order_no, 
          "notify_url" => $this->notify_url,
          "amount" => [
              "total" => (int)(($pay_money*1000)/10),
              "currency" => "CNY", 
          ]
      ];
      $appId=$this->APPID;
      // 商户相关配置
      $merchantId = $this->MCHID; // 服务商商户号或直连商户商户号
      $merchantSerialNumber = '2347F7DF068BBB47AA95A66EF79A7AFCF6BC8779'; // 服务商或直连商户商户API证书序列号
      $PemUtil=new PemUtil();
      $merchantPrivateKey = $PemUtil->loadPrivateKey(dirname(__FILE__).'/wechatpay/hcert/apiclient_key.pem'); // 服务商或直连商户商户私钥
      // 微信支付平台配置
      $wechatpayCertificate = $PemUtil->loadCertificate(dirname(__FILE__).'/wechatpay/hcert/pingtai_cert.pem'); // 服务商或直连商户微信支付平台证书
      // 构造一个WechatPayMiddleware
      $wechatpayMiddleware = WechatPayMiddleware::builder()
          ->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
          ->withWechatPay([ $wechatpayCertificate ]) // 可传入多个微信支付平台证书，参数类型为array
          ->build();

      // 将WechatPayMiddleware添加到Guzzle的HandlerStack中
      $stack = \GuzzleHttp\HandlerStack::create();
      $stack->push($wechatpayMiddleware, 'wechatpay');
      // 创建Guzzle HTTP Client时，将HandlerStack传入
      $client = new \GuzzleHttp\Client(['handler' => $stack]);
      try {
        $wechat_url='https://api.mch.weixin.qq.com/v3/pay/transactions/app';
        $resp = $client->request(
            'POST',
            $wechat_url, //请求URL
            [
                // JSON请求体
                'json' => $json_data,
                'headers' => [ 'Accept' => 'application/json' ]
            ]
        );
        
        $statusCode = $resp->getStatusCode();
        if ($statusCode == 200) { //处理成功
            //echo "success,return body = " . $resp->getBody()->getContents()."\n";
            $package=json_decode($resp->getBody()->getContents(),true);
            //私钥地址
            $mch_private_key =dirname(__FILE__).'/wechatpay/hcert/apiclient_key.pem';
            //当前时间戳
            $timeStamp ="".time()."";
            //随机字符串
            $nonceStr = self::createNoncestr();

            $package1=$package['prepay_id'];
            $message =
                $appId."\n".
                $timeStamp."\n".
                $nonceStr."\n".
                $package1."\n";
            //生成签名
            openssl_sign($message, $raw_sign, file_get_contents($mch_private_key), 'sha256WithRSAEncryption');
            $sign = base64_encode($raw_sign);
            
            $data_ll=array(
                'appid' =>$appId,
                'partnerid'=>(string)$this->MCHID,
                'timestamp' =>$timeStamp,
                'noncestr' =>$nonceStr,
                'prepayid' =>$package['prepay_id'],
                'package'=>'Sign=WXPay',
                'sign'=>(string)$sign,
            );
            return $data_ll;
        }
    } catch (RequestException $e) {
        // 进行错误处理
        echo $e->getMessage()."\n";
        if ($e->hasResponse()) {
            echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
        }
        return;
    }
  }

  /**
   *  作用：产生随机字符串，不长于32位
   */
  static protected function createNoncestr($length = 32)
  {
     $rand='';
     $randstr= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
     $max = strlen($randstr)-1;
     mt_srand((double)microtime()*1000000);
     for($i=0;$i<$length;$i++) {
         $rand.=$randstr[mt_rand(0,$max)];
     }
     return $rand;
  }


  /**
     **$order_sn 原订单号
     **$order_sn 现订单号
     **$totalFee 价格
     **$openid
     **/
    //统一退款
    public function preRefundPay($transaction_id,$out_refund_no,$totalFee,$refundFee){
        $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
        $nonce_str = self::createNoncestr(32);
        $data['appid'] =$this->APPID;
        $data['mch_id'] = $this->MCHID;
        $data['nonce_str'] = $nonce_str;
        $data['transaction_id'] = $transaction_id;
        $data['out_refund_no'] = $out_refund_no;
        $data['total_fee'] = $totalFee*100;
        $data['refund_fee'] = $refundFee*100;
        $data['sign'] = self::sign($data,$this->KEY);
        $xml = self::arrayToXml($data);
        $response = self::postXmlCurl($xml, $url,30,1);
        $array=self::xmlstr_to_array($response);
        Log::write('微信退款回调：'.var_export($array, true));
        return $array;
    }

    // xml转成数组
    static function xmlstr_to_array($xmlstr){
        //禁止引用外部xml实体

        libxml_disable_entity_loader(true);

        $xmlstring = simplexml_load_string($xmlstr, 'SimpleXMLElement', LIBXML_NOCDATA);

        $val = json_decode(json_encode($xmlstring),true);

        return $val;


    }

    // post https请求，CURLOPT_POSTFIELDS xml格式
    static function postXmlCurl($xml, $url, $second = 30,$is_zs=''){
        // 初始化curl
        $ch = curl_init();
        // 超时时间
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        // 这里设置代理，如果有的话
        // curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        // curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);

        if($is_zs){
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');//证书类型
            curl_setopt($ch, CURLOPT_SSLCERT, dirname(__FILE__).'/wechatpay/hcert/apiclient_cert.pem');//证书位置
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');//CURLOPT_SSLKEY中规定的私钥的加密类型
            curl_setopt($ch, CURLOPT_SSLKEY, dirname(__FILE__).'/wechatpay/hcert/apiclient_key.pem');//证书位置
        }

        // 设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        // 要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        // post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        // 运行curl
        $data = curl_exec($ch);

        // 返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            return false;
        }
    }

    //生成签名
    static function sign($Obj,$KEY){
        foreach ($Obj as $k => $v) {
            $Parameters[strtolower($k)] = $v;
        }
        // 签名步骤一：按字典序排序参数
        ksort($Parameters);
        $String = self::formatBizQueryParaMap($Parameters, false);
        // 签名步骤二：在string后加入KEY
        $String = $String . "&key=" . $KEY;
        // 签名步骤三：MD5加密
        $result_ = strtoupper(md5($String));
        return $result_;
    }
    // 将数组转成uri字符串
    static function formatBizQueryParaMap($paraMap, $urlencode){
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($urlencode) {
                $v = urlencode($v);
            }
            $buff .= strtolower($k) . "=" . $v . "&";
        }
        $reqPar;
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

    // 数组转xml
    static function arrayToXml($arr){
        $xml = "<xml>";
        foreach ($arr as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
        }
        $xml .= "</xml>";
        return $xml;
    }
}